#region Usings
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
using Kaleta.Network;
using Lidgren.Network;
using Kaleta.Xna.Timing;
using Kaleta.Xna.Debug;
using Kaleta.Xna;
using Kaleta.Xna.Interfaces;
using Kaleta.Outcast.Debuging;
using Kaleta.Network.Serialization;
using Kaleta.Outcast.Network;
#endregion

namespace Kaleta.Outcast
{
    public class Outcast : Microsoft.Xna.Framework.Game
    {
        #region Fields
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        Storyboard storyboard = new Storyboard();
        KeyboardInput input;
        Ship localShip;
        #endregion

        #region Initialization & Loading content

        public Outcast()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

       

        protected override void Initialize()
        {

            input = new KeyboardInput(Keyboard.GetState(), Keys.F1, Keys.F2, Keys.Up, Keys.Down, Keys.Right, Keys.Left, Keys.Space);

            this.IsFixedTimeStep = false;
            this.graphics.SynchronizeWithVerticalRetrace = false;
            this.InactiveSleepTime = System.TimeSpan.Zero;

            DistributionSerializer.CustomTypesForSerialization.Add(new KeyValuePair<Type, IDataTypeSerialization>(
                typeof(Vector2), new Vector2DataSerialization()));
            DistributionSerializer.CustomTypesForSerialization.Add(new KeyValuePair<Type, IDataTypeSerialization>(
                typeof(ShipSyncData), new ShipSyncDataSerialization()));

            base.Initialize();
        }

        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);
            Debugger.Initialize(Content.Load<SpriteFont>(@"Fonts/Font"));
            Global.Load(Content);

            Debugger.SetValue("F1 - Start server and client. Client automatically connects to server", "");
            Debugger.SetValue("F2 - Start client only. Connect to server", "");

        }
        #endregion


        protected override void Update(GameTime gameTime)
        {
            #region Network creation
            if (input.IsPressed(Keys.F1))
            {
                CreateServer();
                CreateClient();

            }

            if (input.IsPressed(Keys.F2))
            {
                CreateClient();
            }
            #endregion

            #region Input handling
            if (localShip != null)
            {
                if (input.IsDown(Keys.Up))
                    localShip.Accelerate();
                if (input.IsDown(Keys.Down))
                    localShip.Break();
                if (input.IsDown(Keys.Right))
                    localShip.RotateRight();
                if (input.IsDown(Keys.Left))
                    localShip.RotateLeft();

                if (input.IsUp(Keys.Up))
                    if (localShip.MovementState.HasFlag(MovementState.Accelerating))
                        localShip.MovementState &= ~MovementState.Accelerating;
                if (input.IsUp(Keys.Down))
                    if (localShip.MovementState.HasFlag(MovementState.Breaking))
                        localShip.MovementState &= ~MovementState.Breaking;
                if (input.IsUp(Keys.Right))
                    if (localShip.MovementState.HasFlag(MovementState.RotatingRight))
                        localShip.MovementState &= ~MovementState.RotatingRight;
                if (input.IsUp(Keys.Left))
                    if (localShip.MovementState.HasFlag(MovementState.RotatingLeft))
                        localShip.MovementState &= ~MovementState.RotatingLeft;

                if (input.IsPressed(Keys.Space))
                    localShip.Fire();
            }
            #endregion

            #region Client update
            Timer.Run("Update.Client", gameTime, Global.FrameUpdateTime, delegate()
            {
                #region Updating
                if (DistributionSession.Client != null && DistributionSession.Client.Status == NetConnectionStatus.Connected)
                {
                    foreach (var obj in DistributionSession.DistributableObjects.Values)
                    {
                        IUpdatable iUpdateOjb = obj as IUpdatable;
                        if (iUpdateOjb != null)
                            iUpdateOjb.Update(gameTime);
                    }
                    if (localShip != null)
                        localShip.SynchronizeWithServer();
                }

                input.Update(Keyboard.GetState(), gameTime);
                storyboard.Update(gameTime);

                #endregion
            });
            #endregion

            #region Host update
            if (DistributionSession.Server != null)
            {
                Timer.Run("Update.Host", gameTime, Global.FrameUpdateTime, delegate()
                {
                    #region Updating
                    foreach (var obj in DistributionSession.DistributableObjects.Values)
                    {
                        //handle collisions
                    }

                    #endregion
                });
            }
            #endregion

            #region Debug info
            if (DistributionSession.Client != null && DistributionSession.Client.Status == NetConnectionStatus.Connected)
            {
                if (DistributionSession.Server != null)
                    DistributionSession.Server.SimulatedMinimumLatency = Global.SimulatedLatency;

                DistributionSession.Client.SimulatedMinimumLatency = Global.SimulatedLatency;

                DistributionSession.LatencyTester.RunTest();
                DistributionSession.LatencyTester.Update();

                Timer.Run("Latency", gameTime, 1000, delegate()
                {
                    Debugger.SetValue("Roundtrip Latency", DistributionSession.LatencyTester.GetLatency());

                });
            }
            Debugger.SetValue("FPS", Debugger.GetFPS());

            DebugUpdater.Update(gameTime);
            #endregion

            Timer.Run("Update.Network", gameTime, Global.NetworkUpdateTime, delegate()
            {
                UpdateNetwork();
            });

            base.Update(gameTime);
        }

       

        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            spriteBatch.Begin();

            foreach (var obj in DistributionSession.DistributableObjects.Values)
            {
                IRenderable iDrawOjb = obj as IRenderable;
                if (iDrawOjb != null)
                    iDrawOjb.Draw(spriteBatch);
            }

            spriteBatch.End();
            
            Debugger.Draw(spriteBatch);

            base.Draw(gameTime);
        }

        private void CreateGameObjects()
        {
            ActionTimeline action = new ActionTimeline(Global.SimulatedLatency*2000f+Global.NetworkUpdateTime*3, delegate()
            {
                Random random = new Random(DateTime.Now.GetHashCode());

                localShip = new Ship();
                localShip.Position = new Vector2(random.Next(500) + 100, 300);
                localShip.ghostPosition = localShip.Position;

                localShip.Direction = random.Next(200);

                DistributionSession.DistributeObject(localShip);
            });
            this.storyboard.Add(action);
        }

        #region Network handling
        private void CreateServer()
        {
            if (DistributionSession.Server != null) return;

            NetConfiguration serverConfig = new NetConfiguration(this.GetType().Name);
            serverConfig.Port = 666;
            serverConfig.MaxConnections = 4;
            NetServer server = new NetServer(serverConfig);
            server.Start();

            Debugger.SetValue("Server", "started");

            DistributionSession.Initialize(server,
                new Type[] { typeof(Ship), typeof(Bullet) });

        }

        private void CreateClient()
        {
            if (DistributionSession.Client != null) return;

            NetConfiguration clientConfig = new NetConfiguration(this.GetType().Name);
            NetClient client = new NetClient(clientConfig);
            client.Start();

            Debugger.SetValue("Client", "started");

            //client.DiscoverKnownServer("89.173.83.229", 666);
            client.DiscoverKnownServer("91.115.169.74", 666);

            DistributionSession.Initialize(client,
                new Type[] { typeof(Ship), typeof(Bullet) });

            DistributionSession.ServerDataRecieved += new EventHandler<NetworkDataRecievedEventArgs>(DistributionSession_ServerDataRecieved);

            CreateGameObjects();
        }

        void DistributionSession_ServerDataRecieved(object sender, NetworkDataRecievedEventArgs e)
        {
            if (e.NetMessageType == NetMessageType.ServerDiscovered)
            {
                Debugger.SetValue("Client", "Connecting..");
                DistributionSession.Client.Connect(e.ReadBuffer.ReadIPEndPoint());
            }
            else if (e.NetMessageType == NetMessageType.ServerDiscovered)
                Debugger.SetValue("Client", "Server discovered..");
            else
                Debugger.SetValue("Client", "Status: " + DistributionSession.Client.Status.ToString());

        }

        private void UpdateNetwork()
        {
            if (DistributionSession.Server != null)
                DistributionSession.ServerUpdate();

            if (DistributionSession.Client != null)
                DistributionSession.ClientUpdate();

           
        }

        #endregion

        #region Unload Content
        protected override void UnloadContent()
        {
            if (DistributionSession.Server != null)
                DistributionSession.Server.Shutdown("Server disconnected");

            if (DistributionSession.Client != null)
                DistributionSession.Client.Shutdown("Client disconnected");
        }
        #endregion
    }
}
